import json
import random
import re
import urllib
from collections import defaultdict
from datetime import timedelta, datetime
from json import JSONDecodeError

import aiohttp
import time
import sys
import aioredis
import lxml.html
import pymongo
from tornado import gen
import asyncio
from faker import Faker
INTEVAL = 0.2
QUERY_TIME = 60 * 6
lock = asyncio.Lock()

from baseHandler import init_logger, PayHandler, Application

card = {'100': {'cardValCode': '04',
                'cardValue': '10000',
                'offerRate': '1',
                'offerRateId': ''},
        '20': {'cardValCode': '01',
               'cardValue': '2000',
               'offerRate': '1',
               'offerRateId': ''},
        '30': {'cardValCode': '02',
               'cardValue': '3000',
               'offerRate': '1',
               'offerRateId': ''},
        '300': {'cardValCode': '05',
                'cardValue': '30000',
                'offerRate': '1',
                'offerRateId': ''},
        '50': {'cardValCode': '03',
               'cardValue': '5000',
               'offerRate': '1',
               'offerRateId': ''}}

RECHANGE_QUEUE = defaultdict(asyncio.Queue)


def get_ts():
    return str(time.time()).replace('.', '')[:13]

async def get_unicom_wx(user_tel, amount, upay_id, redis, ticket=''):
    faker = Faker(locale='zh_CN')
    ip = faker.ipv4()
    headers = {'Cookie': 'upay_user=%s;' % upay_id, 'Host': 'upay.10010.com',
               'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:62.0) Gecko/20100101 Firefox/62.0',
               # "User-Agent": "Mozilla/5.0 (Linux; Android 7.0; MI 5s Build/NRD90M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/64.0.3282.137 Mobile Safari/537.36 wxwork/2.4.16 MicroMessenger/6.3.22 NetType/WIFI Language/zh",
               'X-Requested-With': 'XMLHttpRequest',
               'Referer': 'http://upay.10010.com/npfwap/npfMobWap/bankcharge/index.html?version=wap&desmobile=6672070486447365',
               # 'CLIENT-IP': ip,
               # 'X-FORWARDED-FOR': ip,
               }

    proxys = await redis.smembers('un_proxy', encoding='utf-8')
    if not proxys:
        return {"code": -1, "message": "无可用代理"}
    proxy = random.choice(list(proxys))
    logger.info("代理%s|%s", user_tel, proxy)
    if proxy:
        conn = proxy
    else:
        conn = None
    logger.info(conn)
    async with aiohttp.ClientSession(headers=headers) as session:
        prov_url = 'http://upay.10010.com/npfwap/npfMobWeb/getArea/init?callback=jQuery&commonBean.phoneNo=%s&_=%s' % (user_tel, get_ts())
        async with session.get(prov_url, proxy=conn) as rsp:
            body = await rsp.text()
            area = re.findall('(\d+)', body)
            prov = body.split('X')[2]

        unused = await redis.sismember("unused_un_prov", prov)
        if unused:
            logger.info("%s交费不可用", prov)
            return {"code": -1, "message": "%s交费不可用" % prov}
        if not area:
            return {"code": -1, "message": rsp.content.decode()}
        card_info = card.get(str(amount), {})
        if card_info:
            bussine_type = '06'
            userChooseMode = '0'
            url = 'http://upay.10010.com/npfwap/NpfMob/mobWapBankCharge/wapBankChargeCheck.action'
            url2 = 'http://upay.10010.com/npfwap/NpfMob/mobWapBankCharge/wapBankChargeSubmit.action'
        else:
            bussine_type = '01'
            userChooseMode = ''
            url = 'http://upay.10010.com/npfwap/NpfMob/mobWapBankPay/mobWapPayFeeCheck.action'
            url2 = 'http://upay.10010.com/npfwap/NpfMob/mobWapBankPay/mobWapPayFeeApply.action'
        data = {
            'browserVersion': '',
            'bussineTypeInput': '0',
            'cardBean.cardValue': card_info.get('cardValue', ''),
            'cardBean.cardValueCode': card_info.get('cardValCode', ''),
            'channelKey': '',
            'commonBean.activityType': '',
            'commonBean.areaCode': '',
            'commonBean.bussineType': bussine_type,
            'commonBean.channelCode': 'DEBIT_YEEPAYWAP',
            'commonBean.channelType': '307',
            'commonBean.cityCode': area[1],
            'commonBean.netAccount': '',
            'commonBean.offerate': card_info.get('offerRate', ''),
            'commonBean.offerateId': card_info.get('offerateId', ''),
            'commonBean.orgCode': '01',
            'commonBean.payAmount': amount,
            'commonBean.phoneNo': user_tel,
            'commonBean.provinceCode': area[0],
            'commonBean.reserved1': 'false',
            'commonBean.ticketNo': '',
            'commonBean.userChooseMode': userChooseMode,
            'invoiceBean.card_type': '',
            'invoiceBean.id_cardno': '',
            'invoiceBean.invoice_head': '',
            'invoiceBean.invoice_type': '',
            'invoiceBean.is_mailing': '0',
            'invoiceBean.need_invoice': '0',
            'numberType': '',
            'pointBean.payMethod': '',
            'pointBean.pointNumber': '',
            'ticketNew': ticket,
        }
        # print(data)
        # url = 'http://upay.10010.com/npfwap/NpfMob/mobWapBankCharge/wapBankChargeCheck.action'
        async with session.post(url, data=data, headers=headers, proxy=conn) as rsp:
            res = await rsp.text()
            res = json.loads(res)
            logger.info(res)
            if res['out'] != 'success':
                # if res['out'] == "您的操作太频繁，请稍后再试！":
                #     await redis.srem("un_proxy", proxy)
                return {"code": -1, "message": res['out']}
            secstate = res['secstate']

        # url2 = 'http://upay.10010.com/npfwap/NpfMob/mobWapBankCharge/wapBankChargeSubmit.action'
        data2 = {'browserVersion': '',
                 'channelKey': '',
                 'commonBean.phoneNo': user_tel,
                 'commonBean.provinceCode': area[0],
                 'secstate.state': secstate,
                 'ticketNew': ticket}
        async with session.post(url2, data=data2, headers=headers, proxy=conn) as rsp:
            res = await rsp.text()
            logger.info(res)
            res = json.loads(res)
            # print(res)
            pay_url = res['payUrl']

            logger.info(pay_url)
        head2 = {'Host': 'unipay.10010.com',
                 'Referer': 'http://upay.10010.com/npfwap/npfMobWap/bankcharge/index.html?version=wap&desmobile=6672070486447365',
                 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:62.0) Gecko/20100101 Firefox/62.0'}
        mini_wap_url = "http://unipay.10010.com/udpPortal/miniwappayment/miniWapPaymentChoose.action?" + urllib.parse.urlparse(pay_url).query
        logger.info(mini_wap_url)
        async with session.get(mini_wap_url, headers=head2, proxy=conn) as rsp:
            body = await rsp.text()
            root = lxml.html.fromstring(body)
            inputs = root.xpath("//input")
            data = {i.name: i.value for i in inputs}
            channel = root.xpath('//*[@id="miniwappayment"]/article/section[2]/div[2]')
            channel = channel[0].attrib['paychannel']
            data['paymentInfo.bankSelect'] = channel
            # data['ServicePlantFormId'] = '10'
            logger.info(data)
            data['bankSelect'] = data.get('paymentInfo.bankSelect')
        result_url = 'https://unipay.10010.com/udpPortal/miniwappayment/miniWapPaymentAffirm.action'
        logger.info(result_url)
        head2['Host'] = 'unipay.10010.com'
        head2["Referer"] = mini_wap_url
        head2['Upgrade-Insecure-Requests'] = "1"
        async with session.post(result_url, data=data, headers=head2, proxy=conn) as rsp:
            logger.info(rsp.headers)
            body = await rsp.text()
            # logger.info(body)
            url = re.findall('"(weixin.*)"', body)
            # logger.info(url)
            query_url = re.findall('redirect_url="(.*)"', body)[0]
            # logger.info(url)
            # query_url = query_url.replace("%22", "")
        async with session.post(query_url, headers=head2, proxy=conn) as rsp:
            text = await rsp.text()
            root = lxml.html.fromstring(text)
            ele = root.xpath('/html/body/div/a[1]')[0]
            query_url = 'https://unipay.10010.com/udpPortal/payment/' + ele.attrib['href']
            return {"code": 0, "url": url[0], "query_url": query_url, "prov": prov}


async def unicom_reuslt(query_url):
    headers = {'Host': 'upay.10010.com',
               'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:62.0) Gecko/20100101 Firefox/62.0'}
    p = urllib.parse.urlparse('query_url')
    query_url = ' https://unipay.10010.com/udpNewPortal/miniwappayment/weixinH5Return?' + p.query
    async with aiohttp.ClientSession(headers=headers) as session:
        async with session.get(query_url) as rsp:
            body = await rsp.text()
            # logger.info(body)
            root = lxml.html.fromstring(body)
            form = root.xpath('//form[@name="form1"]')
            if form:
                url = form[0].attrib['action']
                inputs = root.xpath('//input')
                data = {i.attrib['name']: i.attrib['value'] for i in inputs}
                headers['Referer'] = query_url
                async with session.get(url, params=data, headers=headers) as rsp:
                    body = await rsp.text()
                    # logger.info(body)
                    root = lxml.html.fromstring(body)

            order_no = root.xpath('//*[@id="actiOrderNo"]')
            message = ""
            if order_no:
                message = order_no[0].text
            ele = root.xpath("/html/body/article/h6/span")
            if ele:
                text = ele[0].text
                if '已成功充值' in text:
                    orderstate = '0'
                    # pay_time = root.xpath("/html/body/article/ul/li[4]/span[2]")
                    # pay_time_text = ""
                    # if pay_time:
                    #     pay_time_text = pay_time[0].text
                    return orderstate, message or text
            else:
                err_ele = root.xpath("/html/body/div/p/span")
                if '订单支付失败' in err_ele[0].text:
                    orderstate = '8'
                    message = "未支付"
                    return orderstate, message

            return "9", message


class UniWxHandler(PayHandler):
    @gen.coroutine
    def post(self):
        # s_time = self.application.redis.hget("forbidden_time", "start")
        # e_time = self.application.redis.hget("forbidden_time", "end")
        # now = datetime.now()
        # now_seconds = now.hour * 60 * 60 + now.minute * 60 + now.second
        # if s_time and e_time:
        #     if int(s_time) < now_seconds < int(e_time):
        #         return self.write(json.dumps({"message": "不在交易时间内", "ret": -1}))
        db = self.application.db
        exited = yield self.merchant_exit()
        if not exited:
            logger.info("商户不存在")
            return self.write(json.dumps({"message": "商户不存在", "ret": -1}))
        if not self.check_ip():
            return self.write(json.dumps({"message": "ip错误", "ret": -1}))
        if not self.check_sign():
            logger.info("签名验证失败")
            return self.write(json.dumps({"message": "签名验证失败", "ret": -1}))
        data = self.decrypt()
        if not data:
            return self.write({"message": "解密失败", "ret": -1})
        data = json.loads(data)
        logger.info(data)
        arguments = ["payorderno", "amount"]
        for arg in arguments:
            if arg not in data:
                return self.write({"ret": -1, "message": "缺少%s" % arg})

        is_close = yield self.application.redis.get("is_unicom_close", encoding='utf-8')
        if is_close == '1':
            logger.info("%s|通道关闭" % data["payorderno"])
            return self.write({"ret": -1, "message": "通道关闭"})

        obj = yield db.rechange.find_one({"payorderno": data["payorderno"]})

        if obj:
            logger.info(obj)
            return self.write({"message": "订单号重复", "ret": -1})
        logger.info("支付订单%s|%s|%s", self.merchantid, data["payorderno"], data['amount'])
        supplier = self.merchant_info.get("supplier")
        if not supplier:
            return self.write({"message": "无有效供应商", "ret": -1})

        while True:
            try:
                if data["amount"] in ('10', '20', '30', '50', '100', '200', '300', '500'):
                    queue_name = "UN_%s_%s" % ("M2018081416281534235296", data["amount"])
                    logger.info(queue_name)
                    # obj = RECHANGE_QUEUE[queue_name].get_nowait()
                    obj = yield self.application.redis.rpop(queue_name, encoding='utf-8')
                    obj = json.loads(obj)
                else:
                    return self.write({"message": "不支持的金额", "ret": -1})
                # logger.info(obj)
                if not obj:
                    return self.write({'ret': -1, 'message': "无有效订单"})
                if time.time() - obj["time"] > 58:
                    if time.time() - obj['time'] < 60:
                        yield db.rechange.update_one({"ordernumber": obj["ordernumber"]},
                                                     {"$set": {
                                                         "orderstate": "1"
                                                     }})
                    continue
                yield db.rechange.update_one({"ordernumber": obj["ordernumber"]},
                                             {"$set": {
                                                 "orderstate": "8"
                                             }})
                ticket_count = 5
                while ticket_count:
                    try:
                        n = 5
                        while n:
                            upay_data_s = yield self.application.redis_unicom.rpop('tickets')
                            logger.info(upay_data_s)
                            if upay_data_s:
                                upay_data = json.loads(upay_data_s.decode())
                                break
                            else:
                                yield asyncio.sleep(1)
                                n -= 1
                        if not upay_data_s:
                            logger.info("增加upay")
                            return self.write({'ret': -1, 'message': "系统繁忙"})

                        upay = upay_data["upay"]
                        ticket = upay_data['ticket']
                        logger.info("下单%s|%s", upay, obj['ordernumber'])
                        future = get_unicom_wx(obj["tel_number"], obj['amount'], upay, self.application.redis, ticket)
                        result = yield gen.with_timeout(timedelta(seconds=30), future)
                        # logger.info(result)
                        # logger.info("%s|%s|%s", upay, obj["tel_number"], result.get("message"))
                    except aiohttp.ClientConnectorError as e:
                        logger.info("%s|代理失效", obj["tel_number"])
                        result = {'code': -1, 'message': "proxy error"}
                    except JSONDecodeError as e:
                        logger.error(e, exc_info=True)
                        result = {'code': -1, 'message': "proxy error"}
                    except Exception as e:
                        logger.info("%s|取单失败|%s", obj["tel_number"], logger.info(type(e).__name__))
                        logger.error(e, exc_info=True)
                        result = {'code': -2, 'message': "error"}

                    logger.info("%s|%s|%s", upay, obj["tel_number"], result.get("message"))
                    url = ""
                    if result['code'] == 0:
                        wx_url = result['url']
                        query_url = result['query_url']
                        url = 'http://hh.91info.info:8080/charge/pay.php?orderNubmer=%s' % obj['ordernumber']
                        orderstate = '8'
                        self.application.redis_unicom.lpush('tickets', json.dumps(upay_data))
                        break
                    elif result['code'] == -2:
                        self.application.redis_unicom.lpush('tickets', json.dumps(upay_data))
                        orderstate = '1'
                        # message = result['message']
                        wx_url = None
                        query_url = None
                        break
                    else:
                        if "验证码校验异常" in result.get("message", ""):
                            pass
                        else:
                            self.application.redis_unicom.lpush('tickets', json.dumps(upay_data))
                        wx_url = None
                        query_url = None
                        orderstate = '1'
                        if "proxy error" in result.get("message", ""):
                            pass
                        # elif "操作太频繁" in result.get("message", ""):
                        #     pass
                        elif "验证码校验异常" not in result.get("message", ""):
                            break

                        ticket_count -= 1

                res = {"orderstate": orderstate, "message": result.get('message', ''), 'wx_url': url,
                       "query_url": query_url}

                if orderstate == '8':
                    yield db.rechange.update_one({"ordernumber": obj["ordernumber"]},
                                                 {"$set": {
                                                     "payorderno": data["payorderno"],
                                                     "merchantuid": self.merchantid,
                                                     "orderstate": orderstate,
                                                     "orderid": query_url,
                                                     "wx_url": wx_url,
                                                     "pay_backurl": data.get("backurl"),
                                                     "message": result.get('message', ''),
                                                     "prov": result.get("prov")
                                                 }
                                                 })
                if orderstate == '1':
                    yield db.rechange.update_one({"ordernumber": obj["ordernumber"]},
                                                 {"$set": {
                                                     "orderstate": orderstate,
                                                     "message": result.get('message', '')
                                                 }
                                                 })

                return_obj = self.niti_data(res, {"MERCHANTID": self.merchantid})
                return_obj.update({"ret": 0, "message": "success"})
                self.write(return_obj)
                self.finish()
                return
            except asyncio.QueueEmpty:
                return self.write({'ret': -1, 'message': "无有效订单"})


class UnRechargeHandler(PayHandler):
    @gen.coroutine
    def post(self):
        logger.info(self.request.path)
        db = self.application.db
        exited = yield self.merchant_exit()
        if not exited:
            logger.info("商户不存在")
            return self.write(json.dumps({"message": "商户不存在", "ret": -1}))
        if not self.check_ip():
            return self.write(json.dumps({"message": "ip错误", "ret": -1}))
        if not self.check_sign():
            logger.info("签名验证失败")
            return self.write(json.dumps({"message": "签名验证失败", "ret": -1}))
        data = self.decrypt()
        if not data:
            return self.write({"message": "解密失败", "ret": -1})
        data = json.loads(data)
        logger.info(data)
        arguments = ["ordernumber", "amount", "tel_number"]
        for arg in arguments:
            if arg not in data:
                return self.write({"ret": -1, "message": "缺少%s" % arg})
        logger.info("充值%s|%s|%s", data['ordernumber'], data['tel_number'], data['amount'])

        is_risk = yield self.application.redis.sismember("risk_number", data["tel_number"])
        if is_risk:
            logger.info("不到账%s" % data["tel_number"])
            return self.write({"ret": -1, "message": "风险号码"})
        res = self.check_ordernumber(data)
        if res:
            return self.write(res)
        start_time = time.time()

        obj = {
            "tel_number": data["tel_number"],
            "ordernumber": data["ordernumber"],
            "time": start_time,
            "amount": data["amount"],
            "tel_type": "unicom"
        }

        data['time'] = start_time
        data["orderdate"] = datetime.now().strftime('%Y%m%d')
        data["merchantid"] = self.merchantid
        data['tel_type'] = 'unicom'
        data['orderstate'] = '2'
        try:
            yield db.rechange.insert_one(data)
            self.application.redis.lpush('unicom_recharge', json.dumps({"ordernumber": data['ordernumber']}))
        except pymongo.errors.DuplicateKeyError:
            return self.write({"ret": -1, "message": "重复的订单号"})

        if data["amount"] in ('10', '20', '30', '50', '100', '200', '300', '500'):
            queue_name = "UN_%s_%s" % (self.merchantid, data["amount"])
            yield from RECHANGE_QUEUE[queue_name].put(obj)
            logger.info(RECHANGE_QUEUE[queue_name].qsize())
        else:
            return self.write({"message": "不支持的金额", "ret": -1})

        res = {"orderstate": '2', "message": "订单提交成功", }
        return_obj = self.niti_data(res, {"MERCHANTID": self.merchantid})
        return_obj.update({"ret": 0, "message": "success"})
        self.write(return_obj)
        self.finish()


async def query(query_obj, db, redis):
    now = time.time()
    obj = await db.rechange.find_one(query_obj)
    extra = {}
    if not obj:
        orderstate = "1"
        message = "订单号不存在"
        return orderstate, message, extra

    orderstate = obj.get("orderstate", "2")

    if orderstate in ("0", "1", "5", "6"):
        message = obj.get("message", "")
        extra = {
            "tel_number": obj["tel_number"],
            "orderid": obj.get("orderid", ""),
        }
    else:
        async with lock:
            obj = await db.rechange.find_one(query_obj)
            query_time = obj.get("query_time") or obj["time"]
            if time.time() - query_time < 15:
                message = ""
                return orderstate, message, extra

            query_time = time.time()
            await db.rechange.update_one(query_obj, {"$set": {"query_time": query_time}})

        if obj.get('wx_url'):
            # if obj.get("tel_type", "") == 'unicom':
            orderstate, message = await unicom_reuslt(obj['orderid'])
            if orderstate == '9':
                await redis.sadd("unicom_9", obj['ordernumber'])
            if orderstate == '8':
                if query_time - obj['time'] > QUERY_TIME:
                    orderstate = '1'
                else:
                    orderstate = obj.get("orderstate")
            extra = {
                "tel_number": obj["tel_number"],
                "orderid": obj.get("orderid", ""),
                # "url": obj.get("url", "")
            }
        else:
            if time.time() - obj['time'] < 60 and obj.get('orderstate', '2') == '2':
                orderstate = '2'
                message = obj.get("message", "取单中")
            elif time.time() - obj['time'] < 92 and obj.get('orderstate', '2') == '8':
                orderstate = '8'
                message = '获取二维码中'
            else:
                orderstate = '1'
                message = '未支付2'
            extra = {
                "tel_number": obj["tel_number"],
                "orderid": obj.get("orderid", ""),
                # "url": obj.get("url", "")
            }
        await db.rechange.update_one(query_obj, {
            "$set": {"orderstate": orderstate, "query_time": query_time, "message": message}})
    return orderstate, message, extra


class UnicomQuery(PayHandler):
    @gen.coroutine
    def post(self):
        db = self.application.db
        exited = yield self.merchant_exit()
        if not exited:
            logger.info("商户不存在")
            return self.write(json.dumps({"message": "商户不存在", "ret": -1}))
        if not self.check_ip():
            return self.write(json.dumps({"message": "ip错误", "ret": -1}))
        if not self.check_sign():
            logger.info("签名验证失败")
            return self.write(json.dumps({"message": "签名验证失败", "ret": -1}))
        data = self.decrypt()
        if not data:
            return self.write({"message": "解密失败", "ret": -1})
        data = json.loads(data)

        if data.get("ordernumber"):
            query_obj = {"ordernumber": data["ordernumber"]}
        elif data.get("payorderno"):
            query_obj = {"payorderno": data["payorderno"]}

        else:
            return self.write({"message": "无效参数", "ret": -1})
        orderstate, message, extra = yield query(query_obj, db, self.application.redis)
        logger.info("查询%s|%s|%s|%s", data.get("ordernumber"), data.get("payorderno"), orderstate, message)
        res = {"orderstate": orderstate, "message": message, **extra}
        return_obj = self.niti_data(res, {"MERCHANTID": self.merchantid})
        return_obj.update({"ret": 0, "message": "success"})
        self.write(return_obj)


if __name__ == "__main__":
    handlers = [
        (r"/pay/un_recharge", UnRechargeHandler),
        (r"/pay/un_wxqrcode", UniWxHandler),
        (r"/pay/un_query", UnicomQuery),
        # (r"/pay/queue", QueueHandler),
        # (r"/set_querytime/(\d+)", SetTimeHandler)
    ]
    QUERY_TIME = 60 * 6
    application = Application(handlers, debug=False)
    port = sys.argv[1]
    application.listen(port, address="127.0.0.1")
    logger = init_logger("cmcc", "/app/log/cs_unicom/unicom.log")
    logger.info("Run unionpay server ... http://127.0.0.1:%s", port)
    loop = asyncio.get_event_loop()
    application.redis = loop.run_until_complete(
        aioredis.create_redis(address=('localhost', 6379), db=0, loop=loop)
    )
    application.redis_unicom = loop.run_until_complete(
        aioredis.create_redis(address=('localhost', 6379), db=1, loop=loop)
    )
    application.init_with_loop(loop)
    loop.run_forever()
